using Microsoft.Isam.Esent.Interop;
using System;
using System.IO;

namespace Microsoft.Isam.Esent.Serialization
{
	/// <summary>Table import/export format.</summary>
	public enum ImportExportFormat
	{
		/// <summary>Mostly human-readable UTF16 tab-separated data.</summary>
		TSV = 1,

		/// <summary>Binary format.
		/// <b>Achtung: untested!</b></summary>
		Binary = 2,
	}

	/// <summary>This interface represents a database session.</summary>
	/// <remarks><para>The session object allows you to begin transactions, and obtain the cached table objects.</para>
	/// <para>If your application is multi-threaded, you should use separate session for every thread.</para>
	/// <para>If your application is an ASP.NET web application, you should use the <see cref="SessionPool"/> object.</para>
	/// </remarks>
	public interface iSerializerSession : IDisposable
	{
		/// <summary>Same as IDisposable.Dispose, but also detaches the DB just before the JetEndSession call.</summary>
		void DisposeLastSession();

		/// <summary>The EseSerializer who owns this session.</summary>
		EseSerializer serializer { get; }
		/// <summary>Native session ID</summary>
		JET_SESID idSession { get; }
		/// <summary>Native database ID.</summary>
		JET_DBID idDatabase { get; }

		// The 4 functions below return the table cached by this session.

		/// <summary>Get the Cursor object for a table.</summary>
		void getTable<tRow>( out Cursor<tRow> res ) where tRow : new();
		/// <summary>Same as above, but the returned cursor is optionally read-only.</summary>
		void getTable<tRow>( bool bReadOnly, out Cursor<tRow> res ) where tRow : new();
		/// <summary>Get the Recordset object for a table.</summary>
		void getTable<tRow>( out Recordset<tRow> res ) where tRow : new();
		/// <summary>Get the BookmarkedRecordset object for a table.</summary>
		void getTable<tRow>( out BookmarkedRecordset<tRow> res ) where tRow : new();

		/// <summary>Begins a transaction.</summary>
		/// <remarks><para>You must ( commit or rollback ) and/or Dispose() the returned transaction,
		/// otherwise the unmanaged resources will leak pretty fast.</para>
		/// <para>In support of snapshot isolation,
		/// the database engine stores all versions of all modified data in memory
		/// since the time when the oldest active transaction on any session was first started.
		/// It is important to make transactions as short in duration as possible under high load scenarios.
		/// The class library provides <see cref="iSerializerTransaction.LazyCommitAndReopen">the method</see>
		/// to "pulse" the transaction.</para>
		/// <para>It is highly recommended that the application always be in the context of a transaction
		/// when calling ESE APIs that retrieve or update data. If this is not done,
		/// the database engine will automatically wrap each ESE API call of this type
		/// in a transaction on behalf of the application.
		/// The cost of these very short transactions can add up quickly in some cases.</para>
		/// </remarks>
		iSerializerTransaction BeginTransaction();

		/// <summary>If this session is currently in transaction, this property is set to true.</summary>
		bool isInTransaction { get; }

		/// <summary>If this session is currently in transaction, returns the transaction, otherwise returns null.</summary>
		iSerializerTransaction transaction { get; }

		/// <summary>Clear the whole table.</summary>
		/// <param name="idTable"></param>
		/// <returns>Count of the erased records.</returns>
		int ClearTable( JET_TABLEID idTable );

		/// <summary>Import the table from a stream.</summary>
		/// <param name="tRecord"></param>
		/// <param name="stm"></param>
		/// <param name="fmt">Import format</param>
		void ImportTable( Type tRecord, Stream stm, ImportExportFormat fmt );

		/// <summary>Export the whole table to the specified stream.</summary>
		/// <param name="tRecord"></param>
		/// <param name="stm"></param>
		/// <param name="fmt">Export format.</param>
		void ExportTable( Type tRecord, Stream stm, ImportExportFormat fmt );

		/// <summary>Export the specified tables into the ZIP file.</summary>
		/// <param name="tRecords">Record types to export.</param>
		/// <param name="exportFile">Path of the ZIP file to create.</param>
		void ExportTables( Type[] tRecords, string exportFile );

		/// <summary>Import all tables contained inside the ZIP file.</summary>
		/// <remarks>All existing data in those tables will be erased.
		/// You must open all those types in this session before this call.</remarks>
		/// <param name="importFile">ZIP file to read.</param>
		void ImportTables( string importFile );

		/// <summary>Enumerate opened tables.</summary>
		System.Collections.Generic.IEnumerable<pair<Type, string>> GetAllTypes();

		/// <summary>Open the table in this session.</summary>
		/// <remarks>If this type was not added to the <see cref="EseSerializer" /> who owns this session,
		/// it will be added by this method. So, be ready for SerializationException about wrong DB schema.</remarks>
		void AddType( Type tRecord );

		/// <summary>Resolve table name into the record's type.</summary>
		/// <remarks>This function only looks through the tables opened in this session.</remarks>
		/// <param name="strTableName">The table name; the value is case-sensitive.</param>
		/// <returns>Record type, or null if the table was not found.</returns>
		Type GetType( string strTableName );

		/// <summary>Drop and recreate the complete table.</summary>
		/// <remarks>
		/// <para><b>NB!</b> This method will erase all data + invalidate JET_TABLEID in all sessions.
		/// Handle with care, and expect all kind of problems if you're recreating table while other threads uses it.</para>
		/// <para><b>NB!</b> If you have called <see cref="Cursor{T}.CreateOwnCopy"/> or <see cref="Recordset{T}.CreateOwnCursor" /> on cursors/recordsets in this table,
		/// and you're still holding an open copy of the cursor/recordset, this method will fail saying the table is open.</para>
		/// </remarks>
		/// <typeparam name="tRow"></typeparam>
		void recreateTable<tRow>() where tRow : new();
	}

	/// <summary>This interface represents a database transaction.</summary>
	/// <remarks>You must ( Commit() or LazyCommit() or Rollback() ) and/or Dispose() the transaction,
	/// otherwise the unmanaged resources will leak enormously fast.</remarks>
	public interface iSerializerTransaction : IDisposable
	{
		/// <summary>Commit the transaction and flush the transaction log. Doing so will close this transaction.</summary>
		void Commit();

		/// <summary>Commit the transaction,
		/// but this API does not wait for the transaction to be flushed to the transaction log file before returning to the caller.
		/// Doing so will close this transaction.</summary>
		void LazyCommit();

		/// <summary>Rollback the transaction.
		/// Doing so will close this transaction.</summary>
		/// <remarks>IDisposable.Dispose does the same.</remarks>
		void Rollback();

		/// <summary>Call LazyCommit, then re-open this transaction, using the same session.</summary>
		void LazyCommitAndReopen();

		/// <summary>Try to lock the current record.</summary>
		/// <remarks>This is useful if you're going to update this record at some later point in this transaction.</remarks>
		/// <param name="cur">The current record on this cursor will be locked for writing.</param>
		bool TryLockCurrentRecord( EseCursorBase cur );
	}

	/// <summary>Serializer instance for the specific record type</summary>
	public interface iTypeSerializer
	{
		/// <summary>Get the [EseTable] attribute instance.</summary>
		Attributes.EseTableAttribute tableAttribute { get; }

		/// <summary>Get the columns indexed by the specified index.</summary>
		/// <param name="strIndexName">Name of the index.</param>
		/// <returns>Array of indexed columns.</returns>
		EseColumnAttrubuteBase[] getIndexedColumns( string strIndexName );

		/// <summary>Store all fields of the record in the table.</summary>
		/// <param name="cur">The table cursor</param>
		/// <param name="rec">The object to store in the database. The type of the object must match the table.</param>
		/// <param name="bNewRecord">True for INSERT operations, false for UPDATE operations.</param>
		/// <remarks>The writable transaction must be opened before this call.</remarks>
		void Serialize( EseCursorBase cur, object rec, bool bNewRecord );

		/// <summary>Store only the specific field of the record.</summary>
		/// <param name="cur">The table cursor</param>
		/// <param name="rec">The object to store in the database. The type of the object must match the table.</param>
		/// <param name="fName">ESE column name to store in the table.</param>
		/// <remarks>The writable transaction must be opened before this call.</remarks>
		void SerializeField( EseCursorBase cur, object rec, string fName );

		/// <summary>Fetch all object's fields from the current record of the specified table.</summary>
		/// <param name="cur">The table cursor</param>
		/// <param name="rec">The fields / properties of this object will be filled with the values fetched from the database.</param>
		void Deserialize( EseCursorBase cur, object rec );

		/// <summary>Fetch the specific object's field from the current record of the specified table.</summary>
		/// <param name="cur">The table cursor.</param>
		/// <param name="rec">The object to receive the new field value.</param>
		/// <param name="fName">ESE column name to load from the table.</param>
		void DeserializeField( EseCursorBase cur, object rec, string fName );

		/// <summary>Fetch the specific field from the current record of the specified table.</summary>
		/// <param name="cur">The table cursor.</param>
		/// <param name="fName">ESE column name to load from the table.</param>
		/// <returns>The field value.</returns>
		/// <remarks>This method was implemented because sometimes,
		/// e.g. while traversing the DB-backed tree searching for something,
		/// you only need the value of a single field.</remarks>
		object FetchSingleField( EseCursorBase cur, string fName );

		/// <summary>Update the value of the specific field of the current record.</summary>
		/// <param name="cur">The table cursor.</param>
		/// <param name="fName">ESE column name to load from the table.</param>
		/// <param name="value">New field value.</param>
		/// <remarks>
		/// <para>The writable transaction must be opened before this call.</para>
		/// <para>This method was implemented as the performance optimization:
		/// when you only need to update a single field, and you don't have the complete object.</para>
		/// </remarks>
		void SaveSingleField( EseCursorBase cur, string fName, object value );

		/// <summary>Resolve column name to JET_COLUMNID.</summary>
		/// <param name="cur">The table cursor.</param>
		/// <param name="fName">ESE column name to resolve.</param>
		/// <returns>Column ID.</returns>
		JET_COLUMNID GetColumnId( EseCursorBase cur, string fName );

		/// <summary>Read all fields of the record under the cursor.</summary>
		/// <param name="cur">The table cursor.</param>
		/// <returns>Array of column data.</returns>
		/// <remarks>This method should not be used routinely, to read the fields.
		/// It was designed for replicating the records over a network.</remarks>
		byte[][] RawRead( EseCursorBase cur );

		/// <summary>Write all fields of the record.</summary>
		/// <param name="cur">The table cursor.</param>
		/// <param name="data">Array of column data.</param>
		/// <remarks>
		/// <para>This method will first try to construct a search key on the primary index.
		/// If the record is found, this method will update it.
		/// Otherwise, a new record will be inserted into the table.</para>
		/// <para>This method performs very little type checking.
		/// Please, ensure the source data came from the table with exactly the same columns schema.</para>
		/// </remarks>
		void RawWrite( EseCursorBase cur, byte[][] data );

		/// <summary>Get index of the specified field on the record's raw data.</summary>
		/// <param name="fieldName">ESE column name to resolve.</param>
		/// <returns>Zero-based index of this column.</returns>
		int RawIndex( string fieldName );

		/// <summary>Drop the table with all the data, and create the new table.</summary>
		/// <remarks>
		/// <para><b>Achtung!</b> Calling this method will destroy all the data stored in the table.</para>
		/// </remarks>
		void RecreateTable( iSerializerSession sess );
	}
}